﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace AnaxibiaStudios.BeanPermutator.Gray
{
  public class Permutator<T> : PermuteBase<T>
  {
    private ElementMeta[] _elementMeta;

    private IEnumerator<int> _permutations;
    public override IEnumerable<T> Current { get { return _source; } }

    public Permutator(IList<T> source, Func<T, T, bool> canSwap)
      : base(source, canSwap)
    {
      Reset();
    }

    public override void Dispose() { }

    public override void Reset()
    {
      var nextMeta = new ElementMeta[_source.Count];
      for (int i = 0; i < nextMeta.Length; i++)
      {
        nextMeta[i] = new ElementMeta
        {
          Swing = i,
          Index = i,
          Direction = -1,
          Completed = i == 0, //only the zero element is complete to start
          InitialIndex = i,
          Blocked = false
        };

        if (_elementMeta != null)
        {
          int index = _elementMeta[i].Index;
          int init = _elementMeta[i].InitialIndex;

          if (index != init)
          {
            Swap(_elementMeta[index], _elementMeta[init]);
          }
        }
      }

      _elementMeta = nextMeta;
      _permutations = Next().GetEnumerator();
    }

    public override bool MoveNext()
    {
      return _permutations.MoveNext();
    }

    private ElementMeta FirstIncomplete()
    {
      for (int i = _elementMeta.Length - 1; i > 0; i--)
      {
        if (!_elementMeta[i].Completed)
        {
          return _elementMeta[i];
        }
      }

      return null;
    }

    private ElementMeta ReverseMetaLookup(int index)
    {
      if (index < 0 || index > _elementMeta.Length)
        return null;

      return _elementMeta.FirstOrDefault(meta => meta.Index == index);
    }

    private void Swap(ElementMeta meta1, ElementMeta meta2)
    {
      Log("Swap({0}, {1})", _source[meta1.Index], _source[meta2.Index]);

      T swapTemp = _source[meta1.Index];
      _source[meta1.Index] = _source[meta2.Index];
      _source[meta2.Index] = swapTemp;

      int indexTemp = meta1.Index;
      meta1.Index = meta2.Index;
      meta2.Index = indexTemp;
    }

    private void Log(string format, params object[] args)
    {
      Message += string.Format(format, args) + "\r\n";
    }

    // return true if swap occured
    private MoveResult Move(ElementMeta meta)
    {
      Log("Top level move [{0}]", meta);
      return Move(meta, meta.Index + meta.Direction);
    }

    private MoveResult Move(ElementMeta activeElement, int targetPosition)
    {
      if (targetPosition < 0 || targetPosition >= (_source.Count))
      {
        Log("Attempt to move beyond range. meta[{0}], targetPosition[{1}]", activeElement, targetPosition);
        return MoveResult.NoChange;
      }

      var activeValue = _source[activeElement.Index];

      var swapElement = ReverseMetaLookup(targetPosition);
      var swapValue = _source[targetPosition];

      var leftValue = activeElement.Direction < 0 ? swapValue : activeValue;
      var rightValue = activeElement.Direction < 0 ? activeValue : swapValue;

      bool nextIsParent = activeElement.InitialIndex < swapElement.InitialIndex;

      if (nextIsParent)
      {
        if (!swapElement.Blocked)
        {
          throw new ArgumentException("I don't see why we should be swapping with an unblocked parent");
        }

        bool wasSwappable = CanSwap(leftValue, rightValue);
        int fromIndex = activeElement.Index;

        var result = Move(activeElement, targetPosition + activeElement.Direction);

        if (result != MoveResult.Blocked && result != MoveResult.NoChange)
        {
          //jump
          if (wasSwappable)
          {
            var jumped = ReverseMetaLookup(fromIndex);
            Swap(swapElement, jumped);
          }
          else //push
          {
            Swap(activeElement, swapElement);
          }

          swapElement.Swing--;
          EndCheck(swapElement);
        }

        return result;
      }
      else if (!CanSwap(leftValue, rightValue))
      {
        return MoveResult.Blocked;
      }

      Swap(activeElement, swapElement);
      activeElement.Swing--;

      // try pull
      PullBehind(swapElement, -activeElement.Direction);

      if (activeElement.Swing == 0)
      {
        return MoveResult.Completed;
      }

      return MoveResult.Success;
    }

    private void PullBehind(ElementMeta modifiedElement, int directionBack)
    {
      var behind = ReverseMetaLookup(modifiedElement.Index + directionBack);

      if (behind == null || !behind.Blocked)
        return;

      var result = Move(behind, modifiedElement.Index);
      if (result != MoveResult.Blocked && result != MoveResult.NoChange)
      {
        behind.Direction *= -1;
        behind.Swing = behind.InitialIndex - behind.Swing;
      }
    }

    public string Message = string.Empty;

    private IEnumerable<int> Next()
    {
      Log("Returning: {0}", string.Join(string.Empty, Source.Cast<char>().ToArray()));
      yield return 0;

      ElementMeta current;

      while (true)
      {
        var src = _source as List<char>;
        string txt = string.Join("", src.ToArray());

        if (txt == "cbda")
        {
        }

        current = FirstIncomplete();

        if (current == null)
          break;

        current.Blocked = false;

        var moveResult = Move(current);
        switch (moveResult)
        {
          case MoveResult.Blocked:
            current.Blocked = true;
            current.Completed = true;
            current.Direction *= -1;
            current.Swing = current.InitialIndex - current.Swing;
            Log("blocking : {0}", string.Join("", Source.Cast<char>().ToArray()));
            break;
          case MoveResult.Success:
            break;
          case MoveResult.NoChange:
          case MoveResult.Completed:
            current.Completed = true;
            break;
        }

        EndCheck(current);

        if (moveResult == MoveResult.NoChange || moveResult == MoveResult.Blocked)
          continue;

        for (int parentIndex = current.InitialIndex + 1; parentIndex < _elementMeta.Length; parentIndex++)
        {
          _elementMeta[parentIndex].Completed = false;
        }

        // successful move - return
        string str = string.Join(string.Empty, Source.Cast<char>().ToArray());
        Log("Returning: {0}", str);
        yield return current.InitialIndex;
      }
    }

    private void EndCheck(ElementMeta meta)
    {
      if (meta.Swing == 0)
      {
        meta.Direction = -meta.Direction;
        meta.Swing = meta.InitialIndex;
      }
    }
  }
}
